Saltar a Acerca de Scriptia
Estás viendo la página para la etiqueta (o conjunto de etiquetas) tetris or jQuery.
Etiquetas relacionadas:
juegos |
Las pruebas unitarias (unit testing) son necesarias y convenientes, ya programes en Ruby, en PHP, en JavaScript o en Cuenca. En esta notita veremos cómo utilizar QUnit –la biblioteca creada para el testeo del núcleo de jQuery– para testear nuestros propios proyectos.
El documento base
Comencemos pues, por descargar QUnit y preparar un documento HTML con la siguiente estructura:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>My Test Suite</title>
<link rel="Stylesheet" media="screen" href="ruta/a/testsuite.css" />
<script type="text/javascript" src="ruta/a/jquery.js"></script>
<script type="text/javascript" src="ruta/a/testrunner.js"></script>
<style type="text/css" media="screen">
/* <![CDATA[ */
#main {
display: none;
}
/* ]]> */
</style>
</head>
<body>
<h1>My Test Suite</h1>
<h2 id="banner"></h2>
<h2 id="userAgent"></h2>
<div id="main">
</div>
<ol id="tests"></ol>
</body>
</html>
El elemento con id="banner" es utilizado por QUnit para mostrar el resultado global de la ejecución de las pruebas. En id="userAgent" se muestra información relativa al navegador utilizado. La lista y el detalle de la ejecución de cada prueba se genera en id="tests".
En caso de disponer de Firebug, QUnit volcará abundante y útil información en la consola.
Si necesitamos un fragmento de HTML para la ejecución de las pruebas, lo incluiremos en id="main". Ojo: antes de la ejecución de cada test se devuelve el contenido de este elemento a su estado inicial, esto es, no se mantienen los eventos asignados ni las modificaciones realizadas sobre el árbol en tests anteriores.
Nuestro primer test
Al ajo. La ejecución de una prueba implica una llamada a la función test, pasando como parámetros una cadena identificativa y la referencia a una función en la que se realizan las comprobaciones (aserciones).
La más sencilla de las comprobaciones es ok. Si el parámetro que recibe evalúa a true, el resultado se considera satisfactorio.
Poniendo los dos últimos párrafos en código:
test('Prueba minimalista', function(){
ok(1, 'mi mensajito');
});
Incorpora esas líneas a tu documento de pruebas (dentro de un elemento SCRIPT) y échale un vistazo en el navegador. ¿Todo bien?
Comprobaciones varias
Por supuesto disponemos de otros métodos de comprobación de resultados, estos son los fundamentales:
equals(unArgumento, otroArgumento, mensaje)
Comprueba la igualdad de dos argumentos primitivos.
isSet(unArray, otroArray, mensaje)
Comprueba que dos arrays tienen el mismo contenido.
isObject(unObjeto, otroObjeto, mensaje)
Comprueba que dos objetos tienen los mismos valores asignados a las mismas propiedades.
Módulos
Si utilizamos el mismo documento para probar distintas partes de nuestra biblioteca podemos utilizar la función module para dar nombre a las secciones de la suite.
module('Widget de votaciones');
test('tras la inicialización'), function() {
// inicializamos un widget, etc.
equals($('a.rate', widget).length, 5, 'contiene 5 estrellicas');
});
test('etcetera', function() {
// otro test por aquí
});
Expectaciones
Todo lo que puede fallar, falla (de hecho, si practicas el desarrollo basado en pruebas, lo primero que debería hacer cada test es fallar). Para indicar el total de comprobaciones que se realizarán en una prueba, utilizamos la función expect.
module('expectaciones');
test('un test', function() {
expect(3);
ok(1, 'una');
equals(1 + 1, 2, 'dos');
equals(calculoCasiImposible(), 42, 'y tres');
});
De esta manera, si la ejecución de calculoCasiImposible (la tercera comprobación) falla estrepitosamente, la prueba no se da por buena.
Asincronía y pausas
Si realizamos peticiones asíncronas en nuestros tests, necesitaremos detener la ejecución de la serie y reanudarla cuando convenga. Los métodos stop y start hacen lo que su nombre parece indicar.
module('asincronía');
test('con pausa', function() {
expect(2);
ok(1, 'una');
function my_callback(response, status) {
start();
equals(response, '1', 'el servidor devuelve lo que toca');
}
// detenemos la ejecución de las pruebas
stop();
// lanzamos una petición Ajax
$.get(some_url, my_callback);
});
Si quieres empaparte de pruebas escritas con y para QUnit, lo mejor es que descargues una una release completa de jQuery y bucees en sus entrañas.
Y esto ha sido todo. La semana que viene más y más sexy (si cabe).
Días ha que escribí una nota sobre los eventos en jQuery. Y hora es de ampliar dicho artículo con las novedades que la serie 1.2 de jQuery añade al respecto.
Lo de siempre
Recordemos las bases de la asignación y el manejo de eventos en jQuery. La forma básica de asignar un manejador es la siguiente:
$('un.selector')
.bind('click', unaFuncion);
El manejador (unaFuncion) recibe como parámetro el objeto que representa el evento (normalizado para tener acceso común a sus propiedades en los distintos navegadores). La función se ejecuta en el contexto del elemento al que se ha asignado el manejador.
function unaFuncion(e) {
console.log('Contexto', this);
console.log('Evento', e);
}
$('un.selector')
.bind('click', unaFuncion);
Lo nuevo
Con jQuery 1.2 podemos asignar un manejador para varios eventos con una sola llamada a bind(). Utilizamos para ello una lista de nombres de evento separados por espacios. Observa que en el manejador tomamos una decisión basada en la propiedad type del objeto del evento.
function handle(e) {
var $label = $(this).prev();
if (e.type == 'focus') {
$label.hide();
}
else if (this.value == '') {
$label.show();
}
}
$('#login input')
.filter('[type=text], [type=password]')
.bind('focus blur', handle);
Otra novedad interesante es la pseudonombrespaciación de eventos, que se puede practicar utilizando el nombre del evento seguido de un punto y un identificador arbitrario al asignar el manejador.
Y esto, ¿para qué? Para facilitar la eliminación de grupos de manejadores.
// en algún lugar del código de un drag'n'drop
$(document)
.bind('mousemove.rock_n_roll', some_handler)
.bind('mouseup.rock_n_roll', mouseup_handler);
// en el código de finalización del arrastre
$(document)
.unbind('.rock_n_roll');
Y algo que no es exactamente nuevo pero mola todo y se conoce poco: podemos pasar un objeto de datos en la asignación. El manejador lo recibirá en la propiedad data del evento.
function handle_click(e) {
alert(e.data.foo);
}
$.fn.somePlugin(opts) {
var settings = $.extend({
foo: 'bar'
});
this.bind('click', settings, handle_click);
return this;
}
// mostrará "bar" al activar los enlaces
// con clase "tigre"
$('a.tigre').somePlugin();
// mostrará "tolo" al activar los enlaces
// con clase "leon"
$('a.leon').somePlugin({ foo: 'tolo' });
Por último, se incorpora a la API el método triggerHandler que complementa a trigger. La diferencia: triggerHandler dispara los manejadores asignados al evento pero no ejecuta las acciones por defecto del navegador.
Para más y mejor información y, de paso, practicar el inglés: Release: jQuery 1.2/Events.
Un «problema» con el que todo novato de la programación con jQuery se encuentra tarde o temprano (y las listas de correo lo demuestran) es que el contenido cargado (o generado) dinámicamente no dispara los manejadores de eventos asignados en $(document).ready.
Y, hoygan, esto es de lo más normal.
Si repasamos nuestra forma habitual de trabajar con jQuery, y reflexionamos un poquito…
$(document).ready(function() {
$('selecciono.algo')
.bind('click', hazCosasBonitas);
});
… recordaremos que estamos utilizando $(document).ready porque no podemos seleccionar elementos que no existen. De ahí que tengamos que esperar a disponer de un árbol DOM completo. De ahí que los manejadores de eventos no afecten a elementos nuevos.
¿Entonces?
Opciones las hay adecuadas a unos casos y a otros. La más sencilla, para casos sencillos, es inicializar los contenidos nuevos una vez los hayamos insertado en el documento.
Necesitamos, pues, una función que nos permita seleccionar y actuar dentro de un contexto. Recuerda que jQuery provee de selecciones basadas en contexto. Si utilizas
$('a');
seleccionarás todos los enlaces del documento. Pero si usas
$('a', unElementoSelecto);
seleccionarás los enlaces descendientes de unElementoSelecto.
Por tanto, podemos crear nuestra función de inicialización según contexto como sigue.
function initLinks(context) {
$('a', context)
.bind('click', hazCosasBonitas)
}
Que ejecutaremos desde nuestro manejador para $(document).ready, pasando el documento como contexto de búsqueda:
$(document).ready(function() {
initLinks(document);
});
Así que estamos como al principio… pero mucho más cerca del final. Lo único que nos queda por hacer es ejecutar initLinks cuando recuperemos nuevo contenido. Si usamos load, el más simplón de los métodos Ajax de jQuery, basta con usar una función de callback:
function loadNewContent() {
$('#holder').load('ajax-content.html #response p', function() {
// `this` apunta al elemento que acabamos de rellenar
initLinks(this);
});
}
Y esta es la idea básica, que he reflejado en esta pequeña demo en latín.
Por supuesto, hay otras opciones. Si eres de los que para clavar un clavo usa un plugin, échale un vistazo a LiveQuery; si tienes un serio trasiego de elementos vivos, mejor apostar por la delegación de eventos. Pero esas son otras historias que contaré a su debido momento.
Los muchachos de The Cocktail me han invitado a impartir un taller de jQuery en The Cocktail Academy.
Será el jueves 29 de mayo de 2008, a partir de las las 19.30h en los locales de Salamanca, 17 (Madrid). Si piensas asistir (es gratis), inscríbete cuanto antes en el wiki de los talleres.
Una pista de por donde irán los tiros. Hablaremos de:
- selección de elementos, filtrado, etc.;
- eventos a fondo, incluyendo delegación de eventos y sus ventajas;
- navegación por el DOM;
- algún que otro truquito de lo más chachipiruli.
Y por supuesto, de algunas razones para usar (o no usar) jQuery.
Después del taller, estáis todos invitados a pagarme unas cañas.
Para los que preferíamos la vieja interfaz de consulta de la API de jQuery: jQuery API.
Mike Alsup, autor de jQuery form plugin y otras delicias, nos explica cómo crear un plugin para jQuery que cumpla con las condiciones de: no contaminar el espacio de nombres, acepte opciones (y las extienda), mantenga los límites adecuados entre lo público y lo privado y saque provecho del plugin de metadatos. Ahí es nada: A Plugin Development Pattern.
Llega jQuery UI, una colección de componentes reusables y de alta calidad: jQuery UI: Interactions and Widgets.
Y también la versión 1.2.1 de jQuery, con algunas correcciones de bugs sobre la 1.2.
El equipo de desarrollo de jQuery ha publicado la versión 1.2 de la biblioteca. Incorpora algunas novedades que justifican sobradamente el cambio de minor version.
- Selectores
Se incorporan :has(), :header y :animated. Desaparecen los selectores XPath (si los necesitas, puedes usar el plugin de compatibilidad con XPath) y, aprovechando la ocasión, la sintaxis para los selectores por atributo usa sintaxis CSS. Así, a[@class=jfgi] se convierte en a[class=jfgi].
- Atributos
El método val() ha sido mejorado y ahora permite recuperar el valor de elementos SELECT y marcar y desmarcar checkboxes.
- Navegación por el DOM
Nuevos métodos. map() permite la transmutación alquímica de la colección. prevAll() y nextAll() recuperan, respectivamente, los hermanos (siblings) mayores y menores (o anteriores y siguientes, como se prefiera). slice() corta la colección a gusto del consumidor. hasClass('una-clase‘) nos dice si el elemento tiene o no asignada una-clase. andSelf() combina dos colecciones apiladas. contents() recupera los nodos hijos, incluidos los nodos de tipo texto.
- Manipulación
Llegan wrapAll() y wrapInner(). clone() trae una gran novedad: usando clone(true) los elementos clonados mantienen los manejadores de eventos del original.
- Posición
Aterriza offset(), que nos devuelve las coordenadas de un elemento tomando como origen la esquina superior izquierda del viewport. height() y width() también sirven ahora para obtener el tamaño de la ventana y el documento.
- AJAX
Ahora load() permite cargar de modo muy sencillo pedazos de HTML. Usa un selector a continuación de la URL para indicar el filtro: $('#links').load('/Main_Page #p-Getting-Started li'). Con getScript() podemos cargar scripts desde otros dominios, lo que autoriza a getJSON() a utilizar servicios web basados en JSONP. El método serialize() ha sido reescrito para permitir la serialización sencilla de formularios. Se ha incorporado a $.ajax() la opción cache que fuerza el refresco de los datos solicitados.
- Efectos
Ya podemos utilizar valores en em o porcentajes en las animaciones. El plugin (oficial) Color Animations permite realizar animaciones de colorines. stop() detiene las animaciones. Llegan stop(), queue(), dequeue(), las animaciones relativas, las personalizadas y otras maravillas.
- Eventos
El nuevo método triggerHandler() dispara los manejadores de eventos asignados a un elemento sin activar el comportamiento por defecto del elemento. Llegan los eventos con espacio de nombres.
Todos los detalles en jQuery 1.2: jQuery.extend(”Awesome”).
Adrien Gibrat ha tenido a bien crear y compartir una chuletita de jQuery (PDF).
La versión 1.1.4 de jQuery, publicada a finales de agosto, incluye, como es costumbre, algunas mejoras en el rendimiento, pero también (y esto no es tan habitual) algunas novedades interesantes que merece la pena conocer.
Continúa leyendo Novedades en jQuery 1.1.4